﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Foundation;
using CC98.Text.UBB.Elements;
using CC98.Text.UBB.Controls;
using CC98.Text.UBB.Primitives;

namespace CC98.Text.UBB
{
    internal class UBBParser
    {
        public UBBParser()
        {
        }

        #region Elements

        public void RegisterElement(string key, IUBBType elm)
        {
            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentException("Invalid key");
            if (elm == null)
                throw new ArgumentNullException("elm");
            if (this._ubbTypes.ContainsKey(key))
                throw new InvalidOperationException("Key:" + key + " has already existed");
            this._ubbTypes[key] = elm;
        }

        #endregion

        #region parse

        public FrameworkElement Parse(string ubbText)
        {
            if (string.IsNullOrWhiteSpace(ubbText))
                return null;

            return this.BuildControl(this.MatchPieces(this.SplitIntoPieces(ubbText)));
        }

        public async Task<FrameworkElement> ParseAsync(string ubbText)
        {
            if (string.IsNullOrWhiteSpace(ubbText))
                return null;

            var pieces = this.MatchPieces(this.SplitIntoPieces(ubbText));
            FrameworkElement result = null;

            await Windows.UI.Xaml.Window.Current.Dispatcher.RunAsync(
                Windows.UI.Core.CoreDispatcherPriority.Normal,
                new Windows.UI.Core.DispatchedHandler(() => { result = this.BuildControl(pieces); })
                );

            return result;
        }

        #endregion

        #region protected

        protected IEnumerable<UBBPiece> SplitIntoPieces(string ubbText)
        {
            int readIndex = 0;
            
            for (int i = 0, testStartIndex = -1; i < ubbText.Length; ++i)
            {
                if (ubbText[i] == ']' && testStartIndex != -1)
                {
                    // may be a tag
                    if (i - testStartIndex > 1)
                    {
                        if (ubbText[testStartIndex + 1] != '/')
                        {
                            // Start tag
                            var tag = ubbText.Substring(testStartIndex + 1, i - testStartIndex - 1);
                            // Try to find argument
                            int argIndex = tag.IndexOf('=');
                            if (argIndex == -1 || argIndex == tag.Length - 1)
                            {
                                if (this._ubbTypes.ContainsKey(tag))
                                {
                                    if (testStartIndex > readIndex)
                                        yield return new UBBPiece()
                                        {
                                            Type = PieceType.Text,
                                            Content = ubbText.Substring(readIndex, testStartIndex - readIndex),
                                            Argument = null,
                                            MatchedIndex = -1
                                        };
                                    yield return new UBBPiece()
                                    {
                                        Type = PieceType.Start,
                                        Content = tag,
                                        Argument = null,
                                        MatchedIndex = -1
                                    };
                                    // Move index
                                    readIndex = i + 1;
                                    testStartIndex = -1;
                                }
                            }
                            else
                            {
                                var arg = tag.Substring(argIndex + 1, tag.Length - argIndex - 1);
                                tag = tag.Substring(0, argIndex);
                                if (this._ubbTypes.ContainsKey(tag))
                                {
                                    if (testStartIndex > readIndex)
                                        yield return new UBBPiece()
                                        {
                                            Type = PieceType.Text,
                                            Content = ubbText.Substring(readIndex, testStartIndex - readIndex),
                                            Argument = null,
                                            MatchedIndex = -1
                                        };
                                    yield return new UBBPiece()
                                    {
                                        Type = PieceType.Start,
                                        Content = tag,
                                        Argument = arg,
                                        MatchedIndex = -1
                                    };
                                    // Move index
                                    readIndex = i + 1;
                                    testStartIndex = -1;
                                }
                            }
                        }
                        else
                        {
                            // End tag
                            var tag = ubbText.Substring(testStartIndex + 2, i - testStartIndex - 2);
                            // End tag doesn't have arguments
                            if (this._ubbTypes.ContainsKey(tag))
                            {
                                if (testStartIndex > readIndex)
                                    yield return new UBBPiece()
                                    {
                                        Type = PieceType.Text,
                                        Content = ubbText.Substring(readIndex, testStartIndex - readIndex),
                                        Argument = null,
                                        MatchedIndex = -1
                                    };
                                yield return new UBBPiece()
                                {
                                    Type = PieceType.End,
                                    Content = tag,
                                    Argument = null,
                                    MatchedIndex = -1
                                };
                                // Move index
                                readIndex = i + 1;
                                testStartIndex = -1;
                            }
                        }

                    }
                }
                else if (ubbText[i] == '[')
                {
                    testStartIndex = i;
                }
            }

            if (readIndex < ubbText.Length)
                yield return new UBBPiece()
                {
                    Type = PieceType.Text,
                    Content = ubbText.Substring(readIndex),
                    Argument = null,
                    MatchedIndex = -1
                };
        }

        public IList<UBBPiece> MatchPieces(IEnumerable<UBBPiece> pieces)
        {
            List<UBBPiece> pieceList = new List<UBBPiece>();
            Queue<string> textQueue = new Queue<string>();

            // Match all text
            foreach (var piece in pieces)
            {
                if (piece.Type == PieceType.Text)
                {
                    textQueue.Enqueue(piece.Content);
                }
                else if(piece.Type == PieceType.Start)
                {
                    pieceList.Add(piece);
                }
                else
                {
                    bool isMatched = false;

                    for(int i = 0; i < pieceList.Count;++i)
                        if (pieceList[i].MatchedIndex != -1 && pieceList[i].Content == piece.Content)
                        {
                            // Find
                            var matchedPiece = pieceList[i];
                            // add text first
                            if(textQueue.Count > 0)
                            {
                                pieceList.Add(new UBBPiece()
                                    {
                                        Type = PieceType.Text,
                                        Content = string.Concat(textQueue),
                                        Argument = null,
                                        MatchedIndex = -1
                                    });
                                textQueue.Clear();
                            }
                            // Match
                            pieceList[i] = new UBBPiece()
                                {
                                    Type = matchedPiece.Type,
                                    Content = matchedPiece.Content,
                                    Argument = matchedPiece.Argument,
                                    MatchedIndex = pieceList.Count
                                };
                            // Add
                            pieceList.Add(new UBBPiece()
                                {
                                    Type = piece.Type,
                                    Content = piece.Content,
                                    Argument = null,
                                    MatchedIndex = i
                                });
                            isMatched = true;
                            break;
                        }
                    
                    if(!isMatched)
                    {
                        // Not match, treat as a text
                        textQueue.Enqueue(new string('/', 1) + piece.Content);
                    }
                }
            }

            if(textQueue.Count > 0)
            {
                pieceList.Add(new UBBPiece()
                    {
                        Type = PieceType.Text,
                        Content = string.Concat(textQueue),
                        Argument = null,
                        MatchedIndex = -1
                    });
                textQueue.Clear();
            }

            return pieceList;
        }

        protected FrameworkElement BuildControl(IList<UBBPiece> pieces)
        {
            FrameworkElement container = this.BuildContainer();
            FrameworkElement currentControl = container;

            Stack<int> contentStack = new Stack<int>();
            LinkedList<int> attrList = new LinkedList<int>();

            for (int i = 0; i < pieces.Count; ++i)
            {
                if (pieces[i].Type == PieceType.Text)
                {
                    if (currentControl is IElementCollection)
                    {
                        ((IElementCollection)currentControl).Add(pieces[i].Content);
                    }
                    else
                    {
                        throw new InvalidOperationException("Cannot add text:" + pieces[i].Content + " to content element:" + currentControl.GetType().FullName);
                    }
                }
                else if (pieces[i].Type == PieceType.Start)
                {
                    if (pieces[i].MatchedIndex != -1)
                    {
                        if (this._ubbTypes[pieces[i].Content].IsContentElement)
                        {
                            // Content
                            var ctl = ((IUBBContentType)this._ubbTypes[pieces[i].Content]).BuildElement(
                                this._textControlFactory,
                                pieces[i],
                                attrList.Select(p => this._ubbTypes[pieces[p].Content]).Cast<IUBBAttributeType>()
                                );

                            if (currentControl is IElementCollection)
                            {
                                ((IElementCollection)currentControl).Add(ctl);
                                contentStack.Push(i);
                                currentControl = ctl;
                            }
                            else
                            {
                                throw new InvalidOperationException("Cannot add content:" + ctl.GetType().FullName + " to content element:" + currentControl.GetType().FullName);
                            }
                        }
                        else
                        {
                            // Attribute
                            attrList.AddLast(i);
                        }
                    }
                }
                else
                {
                    if (this._ubbTypes[pieces[i].Content].IsContentElement)
                    {
                        // End of content
                        // Remove the content
                        if (contentStack.Count == 0)
                            throw new InvalidOperationException("Content element mismatch: empty stack");
                        if (pieces[contentStack.Peek()].Content != pieces[i].Content)
                            throw new InvalidOperationException("Content element mismatch: start:" + pieces[contentStack.Peek()].Content + " end:" + pieces[i].Content);
                        // Pop
                        contentStack.Pop();
                        currentControl = (FrameworkElement)currentControl.Parent;
                    }
                    else
                    {
                        // End of attribute
                        // Remove the attribute
                        LinkedListNode<int> node = attrList.First;
                        
                        while(node != attrList.Last)
                        {
                            if (pieces[node.Value].Content == pieces[i].Content)
                                break;
                            node = node.Next;
                        }
                        if (node != attrList.Last)
                            attrList.Remove(node);
                        else
                            throw new InvalidOperationException("Attribute mismatch:" + pieces[i].Content);
                    }
                }
            }

            return container;
        }

        protected FrameworkElement BuildContainer()
        {
            return new Container(this._textControlFactory);
        }

        protected Control BuildTextControl(string text)
        {
            return this._textControlFactory.CreateNormalText(text);
        }

        #endregion

        #region private

        private readonly ITextControlFactory _textControlFactory = new DefaultTextFactory();

        private readonly Dictionary<string, IUBBType> _ubbTypes = new Dictionary<string, IUBBType>();

        #endregion
    }
}
